Beginning Programming Series VIII

Part 2 - Graphics Craps, Logic, Buttons, Statictext

© 2004, Brad Moore

author contact:

http://www.freewebz.com/lb-connection/

Home

Random Access Files

Liberty Simple Help2

Scrolling Background

Blood Presure Sim

Cellular Automata

Making Poker Game

Desktop Shortcuts2

Beginners Programming

Demos

Newsletter help

Index


Developing the game

Now that we have the modified version of Roll Dice running using a WINDOW type window instead of a GRAPHICS WINDOW type, we can get down to building the rest of the game. Earlier in this installment we reviewed the rules (which are fairly simple) and considered a GUI for the game. Now we are going to pull these things together and create the game.

We will be using a new control in this game called the STATICTEXT control, which we are going to look at in a moment. We will also be reviewing BUTTON controls. Our game will feature a really basic ABOUT message window and a HELP window, also implemented in a message window - we will review these quickly too.

The STATICTEXT control is basically a label that you (the programmer) can position on a form and display text string within. The user can not alter the contents of the statictext control. On the surface the control sounds fairly basic, but it offers power to the programmer, because as the programmer you can change the text string displayed by the control at will. You can also change the font, font size, font appearance and even the location of the control while the program is running. It is my favorite method of displaying information to the user when I have just a few words to convey.

We will be using four of these controls, one to report what the total of the roll of the dice and another to report the point if we made one, or other results of the roll. The last two will show wins and losses, one statictext control for each.

The statictext command is a control command like BUTTON. It must be issued before the window on which is it to be displayed is created. If you try to use the command on a window that is already open you will get an error. Let's take a look at the syntax for the command. We will refer to the trusty Liberty Basic Helpfile:

Statictext lets you place instructions or labels into your windows.  
This is most often used with a textbox to describe what to type into it.

The syntax of this command is:

  STATICTEXT #handle, "string", xpos, ypos, wide, high

    or

  STATICTEXT #handle.ext, "string", xpos, ypos, wide, high


#handle  -  This must be the same as the #handle of the window you are 
adding the statictext to.  If #handle.ext is used, this allows your program 
to print commands to the statictext control (otherwise all you'll be able 
to do is set it and forget it).

  		
"string"  -  This is the text component of the statictext.

xpos & ypos  -  This is the distance of the statictext in x and y 
(in pixels) from the upper-left corner of the screen.

wide & high  -  This is the width and height of the statictext.  You must 
specify enough width and height to accommodate the text in "string".

Notice, that like the GRAPHICSBOX, the statictext control can (and should) have a handle extension (in the form #handle.ext) assigned to it. This is critical if we are to alter the contents of the control during runtime. As a rule I always use the handle extensions whether I plan to alter the control contents later or not. They help to define the control clearly. Don't forget the #handle portion is ALWAYS the window handle that we are placing the control on. The window must not yet be open.

The next argument of the command is the text string (in the form "string") that we want the statictext control to display. While this argument is required, you are permitted to pass an empty string ("") to the command. Of course if you do this, it is pretty much understood that you will be altering the command to display other text at runtime, otherwise why even place the control on the form?

Xpos and Ypos simply tell Liberty Basic where to place the control on the form in relation to the upper left corner of the form. It is permitted to place the control outside of the forms viewable area. This is usually done to hide a control until it is needed, at which time it can simply be moved into the viewable area of the form using the "!locate" command. In these cases the Xpos or Ypos or both would be greater than the window width and/or window height.

Width and height make room to display the text inside the control. The control should never be less than 10 pixels high. I usually use 20 or 22 pixels height to display the font Arial 10. Width really depends on what you plan to put on the screen. If it is just a few words try 80 or 100 and adjust from there.

The statictext control accepts three "commands" that can be PRINTed to it at runtime. Like the GRAPHICSBOX the commands are set to the control by specifying the window handle and extension and the command for the control. They can be set with the PRINT statement in the form:


	Print #handle.ext, "command"

Or the PRINT statement can be omitted and the command can simply be set to the control with the PRINT understood by Liberty Basic using the following syntax:


	#handle.ext "command"

Notice the comma following the #handle.ext was dropped also in the second form.

The three commands accepted by the control allow the programmer to change the text string displayed (by simply sending another text string to the control), the font and font characteristics and finally the control's location. Here is what the help file says about the commands:

print #handle.ext, "a string"

Change the text displayed on a statictext control with this command.  
This sets the contents (the visible label) of the statictext to be 
"a string".  The handle must be of form #handle.ext so that you can 
print to the control.

print #handle.ext, "!locate x y width height"

Reposition the statictext control in its window.  This only works if 
the control is placed inside window of type window or dialog.  The 
control will not update its size and location until a refresh command 
is sent to the window.  See the included RESIZE.BAS example program.

print #handle.ext, "!font facename pointSize"

Set the control's font to the specified face and point size.  If an 
exact match cannot be found, then Liberty BASIC will try to find a 
close match, with size being of more prominence than face.  For more 
on specifying fonts read How to Specify Fonts

Notice that in the command to change the font (!FONT) and the command to change the location (!LOCATE), both commands are preceded by an exclamation mark. These are required to tell the control that a command is being set, as opposed to a text string to change the text displayed. If you forget the "!" (like I do often enough) you will change the text displayed, not modify the control.

This same set of commands work also on the COMMAND BUTTON control. As a part of the game we will periodically change the caption (text) on the Roll Dice button, and we will use the exact same principles outline above for the statictext control.

Lets consider the button just briefly in review. We know it is also a control and must be placed on the window BEFORE the window is opened. Its syntax is slightly more complex than the statictext, requiring an anchor point and a branch label where the code is located that handles the stuff the program is suppose to do when the user clicks the button. We call the thing that happens in our program when the user clicks on a button the "button click event" and the code that does the work for that event is called the "button click event handler".

Here is a quick review of the command syntax for the button command from the Liberty Basic Helpfile:

BUTTON #handle.ext, "label", [clickHandler], corner, x, y

or...

BUTTON #handle.ext, "label", [branch], corner, x, y, wide, high

Description:

Add a button that has a text label as specified by the string expression 
"label".  When the user clicks on the button, transfer the flow of 
execution to the event handler code at [clickHandler].  The corner 
parameter specifies where to anchor position from.  This is either 
the upper-left, upper-right, lower-left or lower-right corner of the 
window (UL, UR, LL, LR).  The x and y parameters position the button 
relative to the specified corner.  Finally, the wide and high 
parameters are optional.  These allow you to specify the size of the 
button instead of letting Liberty BASIC autosize the buttons for you.

Usage:

Before you actually OPEN the window, each button must be declared with 
a BUTTON statement.  Here is a brief description for each parameter as 
listed above:

#handle.ext - You must use the same handle that will be used for the 
window that the button will belong to, plus an optional unique extension.

"label"   - Type the label desired for the button here.  This may be a 
string literal or any valid string expression.

[clickHandler]  - Specify a branch label here where the button's event 
handler code is.

corner  - UL, UR, LL, or LR specifies which corner of the window to 
anchor the button to.  For example, if LR is used, then the button will 
appear in the lower right corner.  UL = upper left, UR = upper right, 
LL = lower left, and LR = lower right

x, y - These parameters determine how to place the button relative to 
the corner it has been anchored to.

wide, high - These optional parameters determine how wide and high 
the button  will be in pixels. 

Notice a couple things: First, the extension for the handle (i.e. #handle.ext) is not optional in this command. You must have an extension. Also, as with all other cases, the #handle portion must match the window handle where the button is being placed and the command must be specified BEFORE the window has been opened. Second, notice that width and height are optional in this command. Liberty Basic will make the button wide enough and high enough for the caption (i.e. "label"). Personally I like to control these two elements, because it lets me maintain the look I like in my GUI's.

Let me suggest that for the value of "corner", which is the anchor point for the button, you use "UL". This is the same anchor point all the other controls will use by default. This will allow your GUI to hold together with consistent placement of controls in the event the window is resized.

If you refer to the mockup of the game form I presented earlier you will notice that we are going to have several more buttons, and we are going to change the size of others. We will also need to resize the Roll Dice window to make room for the extra controls.

We are ready to begin creating our game. We will not start from scratch, but rather leverage the latest version of the Roll Dice program. I hope you have noticed a pattern in my development practices through the last few installments. It is an excellent method to use, when creating programs (regardless of whether you plan your programs meticulously using flow charts, rough them in with pseudo-code, or simply sit down in front of the computer and start banging out code). The pattern (you could call it a development methodology) is this: build and test your program as you go. Separate it into small, logical components that are easily managed and create these to the point where they will run and test them. Often I will build a form (window) and put the required components on it then run it to see how it looks. I will then put in each branch and maybe the close window event handler, and then run it again. If things look good I will begin adding logic to each of the event handlers, building, running, fixing and repeating. Using this process allows you to more easily manage the debugging process.

I have already worked out the details of the control placement and window size, so I am going to simply splash the code up here. You may want to work out your own version before using this one, just for the learning value, if not, here you go:


NOMAINWIN
WindowWidth = 362
WindowHeight = 226
UpperLeftX = 40
UpperLeftY = 40

STATICTEXT  #main.wins, "Wins: 0", 240, 14, 100, 20
STATICTEXT  #main.losses, "Losses: 0", 240, 36, 100, 20
STATICTEXT  #main.info, "Beginning New Round - Click to start", 18, 112, 200, 20
STATICTEXT  #main.point, "Point has not been made yet", 18, 134, 200, 20
button      #main.quit, "Quit",[quit],UL, 240, 160, 100, 25
button      #main.help, "Help",[help],UL, 240, 130, 100, 25
button      #main.about, "About",[about],UL, 240, 100, 100, 25
button      #main.new, "New Game",[new],UL, 240, 70, 100, 25
button      #main.roll, "Start Round",[roll],UL, 16, 160, 212, 25
graphicbox  #main.dice1, 20, 20, 80, 80
graphicbox  #main.dice2, 140, 20, 80, 80   

Open "Graphic Craps" for window_nf as #main

At this point the code above should replace similar code in your Roll Dice program (copy it from the article and paste it into your program if not). It should run immediately and you will see a GUI similar to the one I presented earlier in the article. My hand drawn version was very crude and basic, but it communicated the basic details. Here is a screen shot of the program running:

Most of the buttons do not work, and some of them, if clicked will cause an error and halt the program. We have explained all the elements of the new GUI. It consists of statictext controls, command buttons and graphicboxes.

My concept of play was to change the caption of the button below the dice (the rolling button) from "Start Round" to "Roll Dice" (or "Roll Dice Again" if required) depending on whether the player was beginning a new round or was rolling additional rolls in the current round.

The "Start Round" button would display initially and after each round ended. Once clicked, it would be replaced with the "Roll Dice" button. Finally if the player was attempting to make a point and needed to roll repeatedly, the caption would be switched to "Roll Dice Again". As soon as the player made point or crapped out it would be changed to "Start Round".

If the player had an instant win or an instant loss, the button caption would not change.

Lets consider how to change the button caption. As you will remember from earlier in this article, we discussed how printing (or sending) a string to the control would replace the control caption. Changing between these various captions is just a simple matter of sending the text string we want to the control - for instance:


#main.roll "Start Round"

will set the caption of the button we use to trigger the roll dice event to "Start Round". We will add this code into the program shortly.

Let us consider the wins and losses labels we added to our window. They will require us to track the win/loss statistics and display them. To do this we will need to have a variable called "wins" and another called "losses". These will need to be initialized at the beginning of the program and will need to be updated each time the player wins or looses. Lets add the initialization of the variables to the top of our code, even before we open the window up:


wins = 0
losses = 0

We also need to remember that the game of craps is about the total of the two dice rolled. They can produce a value between 2 and 12 (inclusive), but we are not calculating the total, simply displaying the dice. We can see the total, but to the computer it is lost. We must include a variable for tracking the total of both dice thrown. For this I used the variable (you guessed it) "total".

It is assigned the value of the first dice just before the dice face is displayed, then to this value the second dice value is added just before it is displayed. This happens with each of the two to four times the dice do their simulated "bounce". This is essentially wasteful of CPU cycles, but it is so small I don't worry about it. What it gets me is the total of the two dice thrown at the end of the throwing. Here is my re-worked [roll] routine:


[roll]
    'roll a random number of times
    playwave "dice01.wav", async
    times = int(rnd(0)*4)+4
    for j = 1 to times
       'choose some dice values
       dice = int(rnd(0)*6)+1
       total = dice
       gosub [showDice1]
       dice = int(rnd(0)*6)+1
       total = total + dice
       gosub [showDice2]
       'put a loop in here to pause for a brief moment (100 millisec)
       t = time$("ms")
       while time$("ms") < t + 100 : wend 
    next j   

Now consider the game logic we began to rough out earlier in this article. We discussed how the game was basically played in two "phases", the first is where we try to make the point and we can actually make an instant win or loss as well. The second is where we have made a point and we are trying to re-roll the point. Remember we discussed that the over all game logic will fit into a large IF-THEN-END IF block evaluating which phase we were actually in based on the value of the variable "phase".

This means that we must create the variable at the beginning of the code and initialize it. Then we must both track and alter the value of the "phase" variable as the game progresses. All of this takes place with in the structure of the IF-THEN-END IF block.

Lets consider the creation and initialization of the "phase" variable. Again this code appears in the program before the window is first created. This is how I did it:


'Main variable initialization
phase = 1  'this tracks whether we are rolling for a point (=1) 
                  'or have established the point and rolling to win (=2)

Notice the comments. These help me to remember what I was doing and why so I can later read, change or borrow from my program. I can not recommend commenting your code strong enough. I should do it more and have often sat scratching my head thinking "why did I do that?" - Comments will save you enormous trouble and headaches in the future. Use them.

This brings us to the main logic portion of the game. It is an extension of the [roll] routine and it is the evaluation of the roll that was just completed. The IF-THEN-END IF code fragment is the beginning piece of this logic. It looks like this:


If Phase = 1 then
   ...do phase 1 processing
Else
   ...do phase 2 processing
End If

In phase one there will be three different conditions we will need to test for. Each condition will be tested using an IF-THEN-END IF construct. The first is instant loss. This is when we roll a 2, 3 or 12. The statement looks like this:


        if total = 2 or total = 3 or total = 12 then
           ...do some processing
        end if

The next case is the instant win. This is when we roll a 7 or and 11. The statement looks like this:


        if total = 7 or total = 11 then
           ...do some processing
        end if

The last case is where we roll any other value and make a point. These values are easily defined as greater than 3 and less than 11 and yet not 7. It might be a stretch to see this, but consider the remaining values: 4, 5, 6, 8, 9 and 10. These all fit into that very definition. The statement looks like this:


        if total > 3 and total < 11 and total <> 7 then
           ...do some processing
        end if

In the last case, we must remember the dice total, since this will be the point we will roll against in the next phase. As I have implied, only in this third case will we enter into the next phase. Lets keep the dice total in the variable "point" and set the value of "phase" to "2". Here is the code unit for the third case so far:


        if total > 3 and total < 11 and total <> 7 then
            phase = 2 
            point = total
        end if

This puts us into the second phase. In this phase the player will roll the dice continually until one of two things happens. They will either roll a seven (and lose), or they will roll their point value again (and win). This condition lends itself well to a nested IF-THEN-END IF construct, where the first case is the outer condition, and then if it is not satisfied, an else statement causes the inner condition to be tested. Here is the code for both cases in the nested IF-THEN-END IF construct:


        if total = 7 then
            ...do some processing
        else
            'check for win
            if total = point then
                 ...do some processing
            else
                'tell the user this did not make point - and to roll again...
                ...do some processing
            end if
        end if

Notice that I have added an additional ELSE clause to the inner nested IF-THEN-END IF statements. This is the processing for when neither condition is met. We need to inform the user that the point was not made and they need to roll again. We will do this right there.

The full logic for both phases laid out (but not complete) looks something like:


If Phase = 1 then
        if total = 2 or total = 3 or total = 12 then
           ...do some processing
        end if

        if total = 7 or total = 11 then
           ...do some processing
        end if

        if total > 3 and total < 11 and total <> 7 then
            phase = 2 
            point = total
        end if
Else
        if total = 7 then
            ...do some processing
        else
            'check for win
            if total = point then
                 ...do some processing
            else
                'tell the user this did not make point - and to roll again...
                ...do some processing
            end if
        end if
End If

There is a lot missing in there. We need to be updating the values of "wins", "losses", "phase" and "point". We also need to be communicating with the user so they understand what has happened and what is expected of them. We discussed some of this communication in the form of altering the caption of the button that rolls the dice, based on the current phase and roll of the dice. We will also need to tell the used of wins and losses and when they make point, and what they actually roll. There is still a lot to add to have this section complete.

One of the problems with this form of interactive development, building the program as you learn is keeping track of where a certain segment of code might go as we modify the program. I have presented the main structure of the game logic and we have recently seen the updated [roll] routine. The game logic follows at the end of the [roll] routine as the next bit of code to be executed.

Lets fill in the blanks and I will explain why each section is built the way it is. Lets begin with phase one and the loss condition. The tasks associated with this condition are fairly basic. Update the "losses" variable (by adding one) and then display the new losses value. Also inform the user what happened and what to do next. The two statictext controls located under the dice are very handy for this. Here is the updated routine (with comments):


        'check for instant loss (2, 3 or 12)
        if total = 2 or total = 3 or total = 12 then
            losses = losses + 1
            #main.losses "Losses = " + str$(losses)
            #main.info "You rolled " + str$(total) + " - you crapped out!"
            #main.point "Click Start to begin new round"
        end if

I will be changing the caption of the button #main.roll at some point, but it was not required in this case, because we are in phase 1 and the caption of the button is always "Start Round". Also, notice that in the update of #main.losses we are putting a numeric value from a numeric variable onto the window. If we just stuck it onto the screen as is the program would crash. The command to update the caption of the statictext control expects a string, so we must convert the numeric value to a string using the str$() function. I am pretty sure we have touched on this before - check out the Glossary for more information on the function.

The next condition, the instant win condition is very similar to the instant loss condition above, as you will observe.


        'check for instant win (7 or 11)
        if total = 7 or total = 11 then
            wins = wins + 1
            #main.wins "Wins = " + str$(wins)
            #main.info "You rolled " + str$(total) + " - you WIN instantly!"
            #main.point "Click Start to begin new round"
        end if

One of the things I noticed when I first tested this code is that the entire string placed into #main.info does not fit. We will address that a bit later, for now, just don't worry about it too much.

The final condition of the first phase is the making of a point. We already added a bit of extra logic to this condition so that we could better flesh-out the conditions and code for the second phase. What we are missing is the altering of the #main.roll button caption and the communication with the user. Here is the rest of that code:


        if total > 3 and total < 11 and total <> 7 then
            'we have made a point - change the phase and note the point
            #main.info "You made Point, click ROLL DICE to match"
            #main.point "The point you are rolling for is " + str$(total)               
            phase = 2 
            point = total
            #main.roll, "Roll Dice"
        end if

Now we will have to consider the slightly more complex code of the second phase. This section is more complex because of the nested IF-THEN-END IF structure needed to achieve proper operation. While I am sure that this section could be written with the same flat structure of evaluation statements similar what we used in the first phase, I thing that we would be tracking and setting at least a couple more flags to insure things are working correctly. That introduces more opportunities for failure, so I opt for the slightly more complex code for the simpler operation.

Here is what we do in the case of an instant loss in the second phase:


        if total = 7 then
            losses = losses + 1
            #main.losses "Losses = " + str$(losses)
            #main.info "You rolled " + str$(total) + " - you crapped out!"
            #main.point "Click Start to begin new round"
            #main.roll, "Start Round"
            phase = 1
        else

Notice that we are again updating the button #main.roll with the original caption, and that we are resetting the phase back to one. Because this is a loss we must increment the "losses" variable.

Continuing with the next level down IF-THEN-END IF code, which evaluates the match point condition we see the following:


            'check for win
            if total = point then
                wins = wins + 1
                #main.wins "Wins = " + str$(wins)
                #main.info "You rolled " + str$(total) + " - you matched the point!"
                #main.point "Thats a WIN - Click Start for new round"
                #main.roll, "Start Round"
                phase = 1
            else

Here again we are updating the button #main.roll with the original caption, as well as resetting the phase back to one just like we did in the case of a loss. Because this is a win we must increment the "wins" variable.

The final condition is the one we fall through to. We were unable to satisfy the first two conditions, so we finally fall through the evaluation statements and run the following code:


                'this did not make point - roll again...
                #main.info "You rolled " + str$(total) + " - you did not match the point"
                #main.roll, "Roll Dice Again"
            end if
        end if

        wait

This little fragment simply updates the #main.roll button caption one last time and reports to the user the value they rolled. Since this is the end of the nested loops, we close them off with a couple "end if" statements and then hit a wait statement and wait again on the user to do something. The entire [roll] routine is presented next:


[roll]
    'roll a random number of times
    playwave "dice01.wav", async
    times = int(rnd(0)*4)+4
    for j = 1 to times
       'choose some dice values
       dice = int(rnd(0)*6)+1
       total = dice
       gosub [showDice1]
       dice = int(rnd(0)*6)+1
       total = total + dice
       gosub [showDice2]
       'put a loop in here to pause for a brief moment (100 millisec)
       t = time$("ms")
       while time$("ms") < t + 100 : wend 
    next j   
    
    'Now we have values for the dice - display the results...
    if phase = 1 then
        'if the phase is 1 then the point is not made
        
        'check for instant loss (2, 3 or 12)
        if total = 2 or total = 3 or total = 12 then
            losses = losses + 1
            #main.losses "Losses = " + str$(losses)
            #main.info "You rolled " + str$(total) + " - you crapped out!"
            #main.point "Click Start to begin new round"
        end if
        
        'check for instant win (7 or 11)
        if total = 7 or total = 11 then
            wins = wins + 1
            #main.wins "Wins = " + str$(wins)
            #main.info "You rolled " + str$(total) + " - you WIN instantly!"
            #main.point "Click Start to begin new round"
        end if
        
        if total > 3 and total < 11 and total <> 7 then
            'we have made a point - change the phase and note the point
            #main.info "You made Point, click ROLL DICE to match"
            #main.point "The point you are rolling for is " + str$(total)               
            phase = 2 
            point = total
            #main.roll, "Roll Dice"
        end if
    else
    
        'check for instant loss
        if total = 7 then
            losses = losses + 1
            #main.losses "Losses = " + str$(losses)
            #main.info "You rolled " + str$(total) + " - you crapped out!"
            #main.point "Click Start to begin new round"
            #main.roll, "Start Round"
            phase = 1
        else
            'check for win
            if total = point then
                wins = wins + 1
                #main.wins "Wins = " + str$(wins)
                #main.info "You rolled " + str$(total) + " - you matched the point!"
                #main.point "Thats a WIN - Click Start for new round"
                #main.roll, "Start Round"
                phase = 1
            else
                'this did not make point - roll again...
                #main.info "You rolled " + str$(total) + " - you did not match the point"
                #main.roll, "Roll Dice Again"
            end if
        end if
        
    end if
        
    wait

I mentioned that much of the text displayed in the #main.info and #main.point statictext controls does not fit, but we could fix it. Let consider this problem now. Since I do not want to reduce the sparse amount information I am sending to the user already, I have chosen to change the font rather than shorten the strings. We can do this by using the FONT command and sending it to the statictext controls. We saw this command above and briefly touched on it. What I would like to do is set the font to a narrow font that is on nearly every person's computer. This must always be a consideration when using different fonts, who has the font, and how can I get it onto people's computers if it is not well distributed? In our case, Arial Narrow will work great. It is shorter and much narrower than regular Arial. Don't forget that when changing fonts on a button or statictext control, you must begin the command with an exclamation mark "!". This tells Liberty Basic that the text that follows is a command and not a text string to display.

The code to change the font needs appear immediately after the window has been opened up. This will give it a global effect for the duration of the program run time. Therefore place the following code just after the window has been opened, but before the first wait statement.


    #main "font ms_sans_serif 10"
    #main.info "!font arial_narrow 10"
    #main.point "!font arial_narrow 10"
    #main.roll "!font ms_sans_serif 10 bold"

This code sets the default font for the window to ms sans serif, 10 points first, then it sets the font to Arial Narrow for the two statictext controls. Finally it sets the button text face for the #main.roll button to bold so that it will stand out.

This really leaves just a little bit of the program left. We are going to use a new element of programming in Liberty Basic called the SUB PROGRAM in the next bit of code. We will be introducing the subject for now. We will cover it more in detail next time.


Home

Random Access Files

Liberty Simple Help2

Scrolling Background

Blood Presure Sim

Cellular Automata

Making Poker Game

Desktop Shortcuts2

Beginners Programming

Demos

Newsletter help

Index